//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
// Garmin and MapSource are registered trademarks or trademarks of Garmin Ltd. or one of its subsidiaries.
//
// Garmin IMG parser.
//

# include "StdAfx.h"
# include "GarminImg.h"
# include "Map.h"
# include "Types.h"
# include "GarminTypes.h"
# include "Load.h"
# include "Log.h"
# include "Status.h"
# include "ParseHelpers.h"
# include "CheckDate.h"
# include "fast_floor.h"
# include "Wrappers.h"
# include "Globals.h"

//# define assert(_x)

# ifdef _DEBUG
void _PrintBytes (const char * _strComment, const BYTE * _pBytes, size_t _cBytes) {
	const size_t cCommentLen = _strComment != NULL ? ::strlen (_strComment) : 0;
	char * const strBuf = (char *) alloca (cCommentLen + _cBytes*3 + 1);

	if (_strComment != NULL)
		::strcpy (strBuf, _strComment);

	for (size_t c = 0; c < _cBytes; ++ c) {
		const int hi = _pBytes [c] >> 4;
		const int lo = _pBytes [c] & 0xF;

		strBuf [cCommentLen + c*3]     = ' ';
		strBuf [cCommentLen + c*3 + 1] = hi <= 9 ? hi + '0' : hi - 10 + 'A';
		strBuf [cCommentLen + c*3 + 2] = lo <= 9 ? lo + '0' : lo - 10 + 'A';
	}
	strBuf [cCommentLen + _cBytes*3] = '\0';

	::OutputDebugString (strBuf);
	::OutputDebugString ("\r\n");
}

void _PrintBits (const char * _strComment, const BYTE * _pBytes, size_t _cBytes) {
	::OutputDebugString (_strComment);
	::OutputDebugString (" ");

	for (int nByte = _cBytes - 1; nByte >= 0; -- nByte) {
		const BYTE bt = _pBytes [nByte];
		for (int nBit = 7; nBit >= 0; -- nBit) {
			::OutputDebugString (((bt >> nBit) & 0x1) != 0 ? "1" : "0");
		}
	}

	::OutputDebugString ("\r\n");
}
# endif // _DEBUG

# include "GarminImgLoader2.inl"

///////////////////////////////

struct ComparePoints {
	bool operator () (const point_t & _1, const point_t & _2) const {
		return _1.x != _2.x ? _1.x > _2.x : _1.y > _2.y;
	}
};

///////////////////////////////

struct img_submap_t {
	size_t cRgnOffset, cRgnLength;
	size_t cTreOffset, cTreLength;
	size_t cLblOffset, cLblLength;
	size_t cNetOffset, cNetLength;
	size_t cNodOffset, cNodLength;

	img_submap_t () {::memset (this, 0, sizeof (img_submap_t));}
};

/////////////////////////////////////////////////////////

class CGarminImgLoader : public CMapLoader {
  public:
	// Override.
	virtual bool IsValid (const char * _pData, size_t _cFileSize) const;
	virtual LoadStatus_t Load (const char * _pData, size_t _cFileSize, const char * _strFileName, map_t * _pMap);

	LoadStatus_t _Load (const BYTE * _pData, size_t _cFileSize, const char * _strFileName, map_t * _pMap);
	LoadStatus_t _LoadSubmap (const string_t & _strSubfileName, const img_submap_t & _sm, size_t _cProgressSize, size_t _cProgressStart);

  private:
	rgn_t & AddPoint (size_t _cLevel, USHORT _usType, const point_t & _pt,                const char * _strLabel,                      size_t _cBeginPos, size_t _cSize);
	rgn_t & AddPoly  (size_t _cLevel, USHORT _usType, RGN_KIND _Kind, points_t & _points, const char * _strLabel, bool _bDirIndicator, size_t _cBeginPos, size_t _cSize);

	map_t * m_pMap;
	const BYTE * m_pStart, * m_pEnd;

	bool m_b150;

	// The counter of objects.
//	size_t m_cOrdinal;

	file_refs_t m_file_refs;
};

class CGarminRgnLoader : public CGarminImgLoader {
  public:
	// Override.
	virtual bool IsValid (const char * _pData, size_t _cFileSize) const {
		return CGarminImgLoader::IsValid (_pData + 0x3C, _cFileSize - 0x3C);
	}
	virtual LoadStatus_t Load (const char * _pData, size_t _cFileSize, const char * _strMapFileName, map_t * _pMap) {
		return CGarminImgLoader::Load (_pData + 0x3C, _cFileSize - 0x3C, _strMapFileName, _pMap);
	}
};

DECLARE_MAP_LOADER (CGarminImgLoader, "garmin-img", "img", "Garmin MapSource map (*.img)");
DECLARE_MAP_LOADER (CGarminRgnLoader, "garmin-rgn", "rgn", "Garmin MapSource POI map (*.rgn)");

bool CGarminImgLoader::IsValid (const char * _pData, size_t _cFileSize) const {
	if (_cFileSize < 0x200)
		return false;

	// Decode the signature.
	const BYTE btXORMask = _pData [0x10] ^ BYTE ('D');
	char strSignature [6];
	for (size_t c = 0; c < sizeof (strSignature); ++ c)
		strSignature [c] = _pData [0x10 + c] ^ btXORMask;

	// Check the signature.
	if (::memcmp (strSignature, c_strImgSignature, sizeof (strSignature)) == 0)
		return true;

	return false;
}

# define MAXLEVELSCOUNT 9

rgn_t & CGarminImgLoader::AddPoint (
	size_t _cLevel, USHORT _usType, const point_t & _pt, const char * _strLabel,
	size_t _cBeginPos, size_t _cSize
) {
	assert (_cLevel < MAXLEVELSCOUNT);

	rgn_t & rgn = m_pMap->CreateRgn (rkPoint);

	// Initialize the rgn.
	rgn.cBeginPos = _cBeginPos;
	rgn.cEndPos   = _cBeginPos + _cSize;
	rgn.cFileLine = -1;
//	rgn.cOrdinal  = m_cOrdinal ++;
	rgn.pFileRef  = & m_file_refs.back ();

	rgn.SetType (m_pMap->pTypeSet, _usType, rkPoint);

	if (_strLabel && _strLabel [0] != '\0')
		rgn.strLabel = _strLabel;

	rgn.origins.resize (1);
	rgn_origin_t & origin = rgn.origins [0];
	origin.cFileOffset = _cBeginPos;
	origin.cLevel      = _cLevel;
	origin.point       = _pt;

	return rgn;
}

rgn_t & CGarminImgLoader::AddPoly (
	size_t _cLevel, USHORT _usType, RGN_KIND _Kind, points_t & _points, const char * _strLabel, bool _bDirIndicator,
	size_t _cBeginPos, size_t _cSize
) {
	assert (_cLevel < MAXLEVELSCOUNT);

	rgn_t & rgn = m_pMap->CreateRgn (_Kind);

	// Initialize the rgn.
	rgn.cBeginPos = _cBeginPos;
	rgn.cEndPos   = _cBeginPos + _cSize;
	rgn.cFileLine = -1;
//	rgn.cOrdinal  = m_cOrdinal ++;
	rgn.pFileRef  = & m_file_refs.back ();

	rgn.SetType (m_pMap->pTypeSet, _usType, _Kind);

	if (_strLabel && _strLabel [0] != '\0')
		rgn.strLabel = _strLabel;

	if (_bDirIndicator)
		rgn.dwFlags |= rgn_t::fDirIndicator;

	rgn.elements.resize (1);
	rgn_element_t & element = rgn.elements [0];
	element.cFileOffset = _cBeginPos;
	element.cLevel      = _cLevel;
	element.bHole       = false;
	element.points.swap (_points);

	return rgn;
}

LoadStatus_t CGarminImgLoader::Load (const char * _pData, size_t _cFileSize, const char * _strMapFileName, map_t * _pMap) {
	const BYTE btXORMask = _pData [0x10] ^ BYTE ('D');
	if (btXORMask) {
		ReportText ("* XOR:    \t%d", btXORMask);

		//
		// Decode.
		//

		BYTE * const pData2 = new BYTE [_cFileSize];
		CByteArrayHolder bah (pData2);
		::memcpy (pData2, _pData, _cFileSize);
		for (size_t c = 0; c < _cFileSize; ++ c)
			pData2 [c] ^= btXORMask;

		m_b150 = btXORMask == 150;

		return _Load (pData2, _cFileSize, _strMapFileName, _pMap);
	}

	m_b150 = false;
	return _Load (reinterpret_cast<const BYTE *> (_pData), _cFileSize, _strMapFileName, _pMap);
}

LoadStatus_t CGarminImgLoader::_Load (const BYTE * _pData, size_t _cFileSize, const char * _strMapFileName, map_t * _pMap) {
	m_pMap   = _pMap;
	m_pStart = _pData;
	m_pEnd   = _pData + _cFileSize;

	m_file_refs.clear ();
	m_file_refs.push_back (file_ref_t ());
	m_file_refs.back ().strFileName = _strMapFileName;
	m_file_refs.back ().bAttachment = false;

//	m_cOrdinal = 0;

	SetProgressStatus (0);

	// Check the integrity.
//	BYTE btCheckSum = 0;
//	for (size_t c = 0; c < _cFileSize; ++ c)
//		btCheckSum += _pData [c];
//	if (btCheckSum != 0)
//		ReportWarning ("Checksum is incorrect.");


	///////////////////////////////////////////////////////////
	//
	// Parse file header.
	//

	const CImgHeader * const pImgHeader = reinterpret_cast<const CImgHeader *> (_pData);
	m_pMap->pTypeSet = & g_tsGarmin;
	m_pMap->Header.ElevationUnits = unFeet;

	// Name.
	m_pMap->Header.strName.assign (
		pImgHeader->strName,
		GetLineLen (
			pImgHeader->strName,
			pImgHeader->strName + sizeof (pImgHeader->strName)
		)
	);
	m_pMap->Header.strName.append (
		pImgHeader->strName2,
		GetLineLen (
			pImgHeader->strName2,
			pImgHeader->strName2 + sizeof (pImgHeader->strName2) - 1
		)
	);

	// Transparency.
	// NOTE: map is assumed transparent until polygon 0x4b ('Background') will be found.
	m_pMap->Header.bTransparent = true;

	// Block size.
	const size_t cBlockSize = pImgHeader->GetBlockSize ();
	ReportText ("* Block size:\t%d", cBlockSize);

	// Update date.
	ReportText (
		"* Updated:\t%02d/%02d",
		pImgHeader->btUpdateYear > 90 ? 1900 + pImgHeader->btUpdateYear : 2000 + pImgHeader->btUpdateYear,
		pImgHeader->btUpdateMonth
	);

	//
	// Scan header sections.
	//

	typedef std::map<string_t, img_submap_t> name2submap_t;
	name2submap_t name2submap;

	char strSubFileName [8];
	strSubFileName [0] = 0;

	// Validate FAT.
	size_t cDataOffset = pImgHeader->dwDataOffset;
	if (cDataOffset == 0) {
		// NOTE: in new IMG files, this field is 0.
		ReportWarning ("No data offset field.");

		// Let us restore its value.
		// First, omit empty blocks.
		cDataOffset = sizeof (CImgHeader) - sizeof (pImgHeader->FAT);
		size_t cIdxBlock;
		for (cIdxBlock = 0; cDataOffset < _cFileSize; ++ cIdxBlock) {
			const CFileIndexBlock & fib = pImgHeader->FAT [cIdxBlock];
			if (fib.btValid != 0)
				break;

			cDataOffset += sizeof (CFileIndexBlock);
		}

		// Second, count non-empty blocks.
		for (; cDataOffset < _cFileSize; ++ cIdxBlock) {
			const CFileIndexBlock & fib = pImgHeader->FAT [cIdxBlock];
			if (fib.btValid != 1)
				break;

			cDataOffset += sizeof (CFileIndexBlock);
		}
	}
	if (
		cDataOffset < sizeof (CImgHeader) || 
		cDataOffset >= _cFileSize
	) {
		ReportWarning ("The file header contains incorrect data offset field.");
		return lsFailed;
	}

	// Explore FAT.
	assert (sizeof (CImgHeader) - sizeof (pImgHeader->FAT) == 0x600);
	assert (sizeof (CFileIndexBlock) == 0x200);
	const size_t cIdxBlocks = (cDataOffset - (sizeof (CImgHeader) - sizeof (pImgHeader->FAT)))/sizeof (CFileIndexBlock);
	for (size_t cIdxBlock = 0; cIdxBlock < cIdxBlocks; ++ cIdxBlock) {
		const CFileIndexBlock & fib = pImgHeader->FAT [cIdxBlock];
		if (fib.btValid != 1 || fib.btIndex > 0)
			continue;

		if (
			::memcmp (fib.strName, "        ", sizeof (fib.strName)) == 0 &&
			::memcmp (fib.strType, "   ",      sizeof (fib.strType)) == 0
		)
			continue;
		const string_t strSubFileName (fib.strName, sizeof (fib.strName));

		if (m_pMap->Header.strID.empty ())
			m_pMap->Header.strID = strSubFileName;

		img_submap_t & submap = name2submap [strSubFileName];

		// TODO: check FAT 'defragmentation'.

		if (fib.wStartBlock*cBlockSize + fib.dwLength > _cFileSize)
			ReportWarning ("The file is damaged: header refers to data out of file end.");

		if (::memcmp        (fib.strType, "RGN", sizeof (fib.strType)) == 0) {
			submap.cRgnOffset = fib.wStartBlock*cBlockSize;
			submap.cRgnLength = fib.dwLength;
		} else if (::memcmp (fib.strType, "TRE", sizeof (fib.strType)) == 0) {
			submap.cTreOffset = fib.wStartBlock*cBlockSize;
			submap.cTreLength = fib.dwLength;
		} else if (::memcmp (fib.strType, "LBL", sizeof (fib.strType)) == 0) {
			submap.cLblOffset = fib.wStartBlock*cBlockSize;
			submap.cLblLength = fib.dwLength;
		} else if (::memcmp (fib.strType, "NET", sizeof (fib.strType)) == 0) {
			submap.cNetOffset = fib.wStartBlock*cBlockSize;
			submap.cNetLength = fib.dwLength;
		} else if (::memcmp (fib.strType, "NOD", sizeof (fib.strType)) == 0) {
			submap.cNodOffset = fib.wStartBlock*cBlockSize;
			submap.cNodLength = fib.dwLength;
		} else if (::memcmp (fib.strType, "GMP", sizeof (fib.strType)) == 0) {
			ReportWarning ("This map is in NT format which is not supported.");
			return lsFailed;
		} else {
			const string_t strSubFileType (fib.strType, sizeof (fib.strType));
			ReportText ("* Defined: \t%s.%s", strSubFileName.c_str (), strSubFileType.c_str ());
		}
	}

	LoadStatus_t ls = lsFailed;
	const size_t cSubmaps = name2submap.size ();
	size_t cSubmap = 0;
	bool bHasMapData = false;
	for (name2submap_t::iterator ns = name2submap.begin (); ns != name2submap.end (); ++ ns) {
		const string_t & strSubName = ns->first;
		const img_submap_t & submap = ns->second;

		if (submap.cRgnOffset == 0 || submap.cTreOffset == 0 || submap.cLblOffset == 0) {
			ReportWarning (0x600, "Sub-map %s is not complete.", strSubName.c_str ());
			continue;
		}

		bHasMapData = true;
		ReportText ("*** Loading sub-map: '%s' ...", strSubName.c_str ());

		if (submap.cNetOffset != 0)
			ReportText ("* Defined: \tNET");
		if (submap.cNodOffset != 0)
			ReportText ("* Defined: \tNOD");

		map_t map;
		map.cHeaderLen            = cDataOffset;
		map.bNoHeader             = false;
		map.pTypeSet              = & g_tsGarmin;
		map.Header.ElevationUnits = unFeet;
		map.Header.bTransparent   = true;
		map.Header.strName        = m_pMap->Header.strName;
		map.Header.strID          = strSubName;

		m_pMap = & map;
		ls = _LoadSubmap (strSubName, submap, 50*(cSubmap + 1)/cSubmaps - 50*cSubmap/cSubmaps, 50*cSubmap/cSubmaps);
		_pMap->Join (map);

		++ cSubmap;
	}

	if (! bHasMapData) {
		ReportWarning (0x600, "The file does not contain any map data.");
		return lsFailed;
	}

	SetProgressStatus (50);

	_pMap->file_refs.swap (m_file_refs);

	return ls;
}

LoadStatus_t CGarminImgLoader::_LoadSubmap (const string_t & _strSubfileName, const img_submap_t & _sm, size_t _cProgressSize, size_t _cProgressStart) {
	///////////////////////////////////////////////////////////
	//
	// Parse TRE sub-file.
	//

	const CTreHeader * const pTreHeader = reinterpret_cast<const CTreHeader *> (m_pStart + _sm.cTreOffset);
	if (::memcmp (pTreHeader->strSignature, "GARMIN TRE", sizeof (pTreHeader->strSignature)) != 0) {
		ReportWarning (_sm.cTreOffset, "Unexpected format: TRE header is not found.");
		return lsFailed;
	}
	if (pTreHeader->wHeaderLength < 0x74)
        ReportWarning ("Unexpected size of TRE header: %d byte(s).", pTreHeader->wHeaderLength);

	// Get map's bounding rectangle.
	rect_t rectMapBound;
	rectMapBound.x0 = ConvertB3ToDeg (pTreHeader->b3MapWest);
	rectMapBound.x1 = ConvertB3ToDeg (pTreHeader->b3MapEast);
	rectMapBound.y1 = ConvertB3ToDeg (pTreHeader->b3MapNorth);
	rectMapBound.y0 = ConvertB3ToDeg (pTreHeader->b3MapSouth);
	if (rectMapBound.x1 == -180.f)
		rectMapBound.x1 = 180.f;
	assert (rectMapBound.x0 < rectMapBound.x1);
	assert (rectMapBound.y0 < rectMapBound.y1);
	float fMapMeanX = rectMapBound.MeanX ();//(rectMapBound.x0 + rectMapBound.x1)/2;

	// Map generator description.
	// NOTE: the string is stored right after the header and before TRE data.
	const char * const strGenerator = reinterpret_cast<const char *> (m_pStart + _sm.cTreOffset + pTreHeader->wHeaderLength);
	if (* strGenerator)
		ReportText ("* Generated:\t%s", strGenerator);

	//
	// Parse zoom levels.
	//
	const size_t cLevels = pTreHeader->dwLevelsLength/sizeof (CTreZoomLevel);
	m_pMap->Levels.resize (cLevels);

	const bool bLocked = (pTreHeader->btVer & 0x80) != 0;
	const CTreZoomLevel * levels = NULL;
	if (bLocked) {
		# include "GarminImgLoader.inl"
			ReportText ("*********************************************************************************");
			ReportWarning ("The map is locked. (NOTE: such maps are not supported by intention).");
			ReportText ("*********************************************************************************");
			return lsFailed;
		}
	} else {
		if (pTreHeader->btVer != 0)
			ReportWarning ("Unexpected format of TRE header: %d", pTreHeader->btVer);

		levels = reinterpret_cast<const CTreZoomLevel *> (m_pStart + _sm.cTreOffset + pTreHeader->dwLevelsOffset);
	}
	CArrayHolder<CTreZoomLevel> levels_holder (bLocked ? levels : NULL);

	size_t cLevel;
	for (cLevel = 0; cLevel < cLevels; ++ cLevel) {
		m_pMap->Levels [cLevel].cZoom = levels [cLevels - 1 - cLevel].btZoom;
		m_pMap->Levels [cLevel].SetBasicScale (levels [cLevels - 1 - cLevel].btBits);
	}

	//
	// Parse groups.
	//
    const BYTE * const pGroupsData = m_pStart + _sm.cTreOffset + pTreHeader->dwGroupsOffset;

	// Count groups except 0-th zoom level.
	size_t cGroupsN = 0;
	for (cLevel = 1; cLevel < cLevels; ++ cLevel)
		cGroupsN += levels [cLevels - 1 - cLevel].wGroups;
	const size_t cGroups0 = levels [cLevels - 1].wGroups;

	struct CGroup {
		size_t cRgnOffset;
		BYTE   btKinds;
		long   lX0, lY0;
		size_t cLevel;
		size_t cShiftBits;

		size_t cPOIsOffset,      cPOIsLength;
		size_t cPointsOffset,    cPointsLength;
		size_t cPolylinesOffset, cPolylinesLength;
		size_t cPolygonsOffset,  cPolygonsLength;
	};
	const size_t cGroups = cGroups0 + cGroupsN;
	CGroup * const groups = new CGroup [cGroups];
	CByteArrayHolder bah (reinterpret_cast<BYTE *> (groups));

	float fTreSize2 = 0;
	size_t cTreGroups = 0;

	// Counter of bytes.
	size_t i;
	// Counter of groups.
	size_t k = 0;
    // Full-length records (all except 0-th zoom level).
    for (i = 0; i < cGroupsN*sizeof (CTreGroup); i += sizeof (CTreGroup)) {
		assert (k < cGroups);

		const CTreGroup * const pTreGroup = reinterpret_cast<const CTreGroup *> (pGroupsData + i);
        groups [k].cRgnOffset = pTreGroup->cRgnOffset;
        groups [k].btKinds    = pTreGroup->btKinds;
        groups [k].lX0        = ConvertB3ToDWORD (pTreGroup->b3X0);
        groups [k].lY0        = ConvertB3ToDWORD (pTreGroup->b3Y0);

        ++ k;
    }
	assert (k == cGroupsN);
    // Short 14-byte records (only for 0-th zoom level).
	assert (i == cGroupsN*sizeof (CTreGroup));
    for (; i + 14 < pTreHeader->dwGroupsLength; i += 14) {
		assert (k < cGroups);
		if (k >= cGroups) {
			ReportWarning ("Unexpected data in TRE header.");
			return lsFailed;
		}

		const CTreGroup * const pTreGroup = reinterpret_cast<const CTreGroup *> (pGroupsData + i);
        groups [k].cRgnOffset = pTreGroup->cRgnOffset;
        groups [k].btKinds    = pTreGroup->btKinds;
        groups [k].lX0        = ConvertB3ToDWORD (pTreGroup->b3X0);
        groups [k].lY0        = ConvertB3ToDWORD (pTreGroup->b3Y0);
		groups [k].cLevel     = 0;

		fTreSize2 += pTreGroup->wWidth*pTreGroup->wWidth + pTreGroup->wHeight*pTreGroup->wHeight;
		cTreGroups += 2;

        ++ k;
    }
	assert (k == cGroups);
    // NOTE: just 4 bytes rest: expected btKind == 0.
	assert (i + 4 == pTreHeader->dwGroupsLength);
	const CTreGroup * const pTreGroup = reinterpret_cast<const CTreGroup *> (pGroupsData + i);
	if (i + 4 < pTreHeader->dwGroupsLength || pTreGroup->btKinds != 0)
		ReportWarning ("Unexpected format: last TRE group is not empty.");

    k = 0;
    for (cLevel = cLevels; cLevel >= 1; -- cLevel) {
		const BYTE btBits = levels [cLevels - cLevel].btBits;
		const size_t cg   = levels [cLevels - cLevel].wGroups;

        for (size_t c = 0; c < cg; ++ c) {
			assert (k < cGroups);
			groups [k].cLevel  = cLevel - 1;
            groups [k].cShiftBits = 24 - btBits;
            ++ k;
        }
    }
	assert (k == cGroups);

	// TreSize.
	m_pMap->Header.cTreSize = cTreGroups != 0 ? fast_floor (::sqrt (fTreSize2/cTreGroups) + .5f) : 511;

	// TRE3 (copyright strings in LBL).
	assert (pTreHeader->wCopyrightsRecSize == 3);
	const size_t cCopyrights = pTreHeader->dwCopyrightsLength/sizeof (CTreCopyright);
	const CTreCopyright * const pCopyrights = reinterpret_cast<const CTreCopyright *> (m_pStart + _sm.cTreOffset + pTreHeader->dwCopyrightsOffset);

	///////////////////////////////////////////////////////////
	//
	// Parse LBL sub-file.
	//

	const CLblHeader * const pLblHeader = reinterpret_cast<const CLblHeader *> (m_pStart + _sm.cLblOffset);
	if (::memcmp (pLblHeader->strSignature, "GARMIN LBL", sizeof (pLblHeader->strSignature)) != 0) {
		ReportWarning (_sm.cLblOffset, "Unexpected format: LBL header is not found.");
		return lsFailed;
	}

	// Optional fields.
	if (pLblHeader->wHeaderLength > 0xB0 && pLblHeader->dwSortingLength != 0) {
		const char * const strSorting = reinterpret_cast<const char *> (m_pStart + _sm.cLblOffset + pLblHeader->dwSortingOffset);
		ReportText ("* Sorting:   \t%s", strSorting);
	}

	// Code page.
	if (pLblHeader->wHeaderLength >= 0xAC)
		m_pMap->Header.wCodePage = pLblHeader->wCodePage;

	// Label coding.
    switch (pLblHeader->btLblCoding) {
		case 6:
			m_pMap->Header.CodeSchema = bpsAscii7;
			m_pMap->Header.wCodePage = 0; // NOTE: some Garmin maps mistakenly have non-ASCII7 code page.
			ReportText ("* Text coding:\t6 bit");
			break;

		case 9:
			m_pMap->Header.CodeSchema = bpsSBCS;
			ReportText ("* Text coding:\t8 bit");
			break;

		case 10:
			m_pMap->Header.CodeSchema = bpsMBCS;
			ReportText ("* Text coding:\t16 bit");
			break;

		default:
			ReportWarning ("Text coding %d-bit per symbol is not supported.", pLblHeader->btLblCoding);
			return lsFailed;
	}

	// Parse labels.
    const BYTE * const pLblData = reinterpret_cast<const BYTE *> (m_pStart + _sm.cLblOffset + pLblHeader->dwDataOffset);
	const size_t cLblDataLength = pLblHeader->dwDataLength;

	typedef std::map<size_t, string_t> labels_t;
	labels_t labels;
	for (size_t cOffset = 0; cOffset < cLblDataLength;) {
		const size_t cLabelOffset = cOffset;

		// For each label.
		int state = 0;
		char strLabel [128];
		size_t pos = 0;
		do {
			SetProgressStatus (_cProgressStart + (20*_cProgressSize/100)*cOffset/cLblDataLength);
			if (g_bStop)
				return lsInterrupted;

			if (pLblHeader->btLblCoding == 6) {
				// Convert 3 bytes into 4 symbols (6 bits each).
				const BYTE bt1 =  (pLblData [cOffset + 0]) >> 2;
				const BYTE bt2 = ((pLblData [cOffset + 0] & 0x03) << 4) + ((pLblData [cOffset + 1]) >> 4);
				const BYTE bt3 = ((pLblData [cOffset + 1] & 0x0F) << 2) + ((pLblData [cOffset + 2]) >> 6);
				const BYTE bt4 =  (pLblData [cOffset + 2] & 0x3F);

				if (bt1 > 0x2F) {
					++ cOffset;
					break;
				}
				Append6bitSymbol (state, bt1, strLabel, pos);
				if (bt2 > 0x2F) {
					++ cOffset;
					break;
				}
				Append6bitSymbol (state, bt2, strLabel, pos);
				if (bt3 > 0x2F) {
					cOffset += 2;
					break;
				}
				Append6bitSymbol (state, bt3, strLabel, pos);
				if (bt4 > 0x2F) {
					cOffset += 3;
					break;
				}
				Append6bitSymbol (state, bt4, strLabel, pos);

				cOffset += 3;
			} else if (pLblHeader->btLblCoding == 9 || pLblHeader->btLblCoding == 10) {
				const BYTE bt = pLblData [cOffset];
				if (bt == 0) {
					++ cOffset;
					break;
				}

				if (pos < sizeof (strLabel) - 7 + 1) {
					switch (bt) {
						// Interstate highway
						case 0x01: ::memcpy (strLabel + pos, "~[0x01]", 7); pos += 7; break;
						// Highway - shield
						case 0x02: ::memcpy (strLabel + pos, "~[0x02]", 7); pos += 7; break;
						// Highway - round symbol
						case 0x03: ::memcpy (strLabel + pos, "~[0x03]", 7); pos += 7; break;
						// Highway - small symbol - blue/red (E-highway).
						case 0x04: ::memcpy (strLabel + pos, "~[0x04]", 7); pos += 7; break;
						// Highway - big symbol (D-highway).
						case 0x05: ::memcpy (strLabel + pos, "~[0x05]", 7); pos += 7; break;
						// Highway - small symbol - white (Internal highway).
						case 0x06: ::memcpy (strLabel + pos, "~[0x06]", 7); pos += 7; break;

						// Only the second part is shown; with the cursor you see the whole word.
						case 0x1b: ::memcpy (strLabel + pos, "~[0x1b]", 7); pos += 7; break;
						// Only the first part is shown; with the cursor you see the whole word.
						case 0x1c: ::memcpy (strLabel + pos, "~[0x1c]", 7); pos += 7; break;
						// In areas type 0x4A = <map_name>1D<number_of_detail_map>
						// In LBL2/LBL3 = <long_name>1D<short_name>
						case 0x1d: ::memcpy (strLabel + pos, "~[0x1d]", 7); pos += 7; break;
						// Only the second part is shown; with the cursor you see the whole word, separated by a blank.
						case 0x1e: ::memcpy (strLabel + pos, "~[0x1e]", 7); pos += 7; break;
						// Only the first part is shown; with the cursor you see the whole word, separated by a blank.
						case 0x1f: ::memcpy (strLabel + pos, "~[0x1f]", 7); pos += 7; break;

						default:
							assert (bt >= 32);
							strLabel [pos ++] = bt;
					}
				}
				++ cOffset;
			}
		} while (cOffset < cLblDataLength);

		// Align next string offset.
		if (pLblHeader->btLblCoding == 6 && pLblHeader->btOffsetMultiplierPowerOf2 == 1) {
			if (cOffset & 0x1)
				++ cOffset;
		}

		// Save the offset -> label association.
		labels [cLabelOffset].assign (strLabel, pos);
	}
	SetProgressStatus (_cProgressStart + (20*_cProgressSize/100));

	// Copyright info (referred by TRE).
	for (size_t cc = 0; cc < cCopyrights; ++ cc) {
		const string_t & strCopyright = labels [ConvertB3ToDWORD (pCopyrights [cc].b3Offset) << pLblHeader->btOffsetMultiplierPowerOf2];

		// Replace "~[0x1f]" to (c).
		char * const str = reinterpret_cast<char *> (alloca ((strCopyright.size () + 1)*sizeof (char)));
		size_t c = 0;
		for (const char * p = strCopyright.c_str (); * p != '\0'; ++ p)
			if (::strncmp (p, "~[0x1f]", 7) == 0) {
				str [c ++] = '\xA9';
				p += 6;
			} else
				str [c ++] = * p;
		str [c] = '\0';

		if (m_pMap->Header.strCopyright.empty ())
			m_pMap->Header.strCopyright = str;
		else {
			m_pMap->Header.strCopyright.append (1, '|');
			m_pMap->Header.strCopyright.append (str);
		}

		if (cc == 0)
			ReportText ("* Copyright info:\t%s", str);
		else
			ReportText ("* \t             \t%s", str);
	}

	// Countries.
	fvector<country_t *> vecCountries;
	size_t cCountries = 0;
	if (pLblHeader->dwCountriesLength) {
		assert (pLblHeader->wCountriesRecSize == sizeof (CLblCountry));
		cCountries = pLblHeader->dwCountriesLength/sizeof (CLblCountry);
		const CLblCountry * const pCountries = reinterpret_cast<const CLblCountry *> (m_pStart + _sm.cLblOffset + pLblHeader->dwCountriesOffset);

		vecCountries.resize (cCountries);
		for (size_t c = 0; c < cCountries; ++ c) {
			m_pMap->Countries.push_back (country_t ());
			country_t & country = m_pMap->Countries.back ();
			vecCountries [c] = & country;

			// Name.
			const DWORD dwNameOffset = ConvertB3ToDWORD (pCountries [c].b3NameOffset);
			const string_t & strName = labels [dwNameOffset << pLblHeader->btOffsetMultiplierPowerOf2];

			const char c_strDelimiter [] = "~[0x1d]";
			const char * const pDelimeter = ::strstr (strName.c_str (), c_strDelimiter);
			if (pDelimeter == NULL) {
				country.strName.assign (strName);
			} else {
				const char * const pAbbreviation = pDelimeter + sizeof (c_strDelimiter)/sizeof (c_strDelimiter [0]) - 1;
				country.strName.assign (strName.c_str (), pDelimeter - strName.c_str ());
				country.strAbbrevName.assign (pAbbreviation);
			}
		}
	}

	// Regions.
	fvector<region_t *> vecRegions;
	size_t cRegions = 0;
	if (pLblHeader->dwRegionsLength) {
		assert (pLblHeader->wRegionsRecSize == sizeof (CLblRegion));
		cRegions = pLblHeader->dwRegionsLength/sizeof (CLblRegion);
		const CLblRegion * const pRegions = reinterpret_cast<const CLblRegion *> (m_pStart + _sm.cLblOffset + pLblHeader->dwRegionsOffset);

		vecRegions.resize (cRegions);
		for (size_t c = 0; c < cRegions; ++ c) {
			m_pMap->Regions.push_back (region_t ());
			region_t & region = m_pMap->Regions.back ();
			vecRegions [c] = & region;

			// Name.
			const DWORD dwNameOffset = ConvertB3ToDWORD (pRegions [c].b3NameOffset);
			const string_t & strName = labels [dwNameOffset << pLblHeader->btOffsetMultiplierPowerOf2];

			const char c_strDelimiter [] = "~[0x1d]";
			const char * const pDelimeter = ::strstr (strName.c_str (), c_strDelimiter);
			if (pDelimeter == NULL) {
				region.strName.assign (strName);
			} else {
				const char * const pAbbreviation = pDelimeter + sizeof (c_strDelimiter)/sizeof (c_strDelimiter [0]) - 1;
				region.strName.assign (strName.c_str (), pDelimeter - strName.c_str ());
				region.strAbbrevName.assign (pAbbreviation);
			}

			// Country.
			const size_t cCountry = pRegions [c].wCountryIndex - 1;
			if (cCountry < cCountries)
				region.pCountry = vecCountries [cCountry];
			else {
				assert (false);
				region.bDeleted = true;
			}
		}
	}

	typedef std::map<country_t *, region_t *> Country2Region_t;
	Country2Region_t DummyRegions;

	// Cities.
	size_t cCities = 0;
	fvector<city_t *> vecCities;
	const CLblCity * pCities = NULL;
	if (pLblHeader->dwCitiesLength) {
		assert (pLblHeader->wCitiesRecSize == sizeof (CLblCity));
		cCities = pLblHeader->dwCitiesLength/sizeof (CLblCity);
		pCities = reinterpret_cast<const CLblCity *> (m_pStart + _sm.cLblOffset + pLblHeader->dwCitiesOffset);

		vecCities.resize (cCities);
		for (size_t c = 0; c < cCities; ++ c) {
			SetProgressStatus (_cProgressStart + (20*_cProgressSize/100) + (10*_cProgressSize/100)*c/cCities);

//			assert (pCities [c].wRegionIndex != 0);
			if (pCities [c].wRegionIndex == 0) { // ??
				vecCities [c] = NULL;
				continue;
			}

			m_pMap->Cities.push_back (city_t ());
			city_t & city = m_pMap->Cities.back ();
			vecCities [c] = & city;

			// Name.
			// NOTE: if pCities [c].bPoint=1, then name of city will be determined after TRE group parsing.
			if (pCities [c].bPoint == 0) {
				const DWORD dwLabelOffset = ConvertB3ToDWORD (pCities [c].b3LabelOffset);
				assert (dwLabelOffset);
				if (dwLabelOffset)
					city.strName = labels [dwLabelOffset << pLblHeader->btOffsetMultiplierPowerOf2];
			}

			// Region.
			if (pCities [c].bNoRegion == 0) {
				const size_t cRegion = pCities [c].wRegionIndex - 1;
				if (cRegion >= cRegions) {
					city.bDeleted = true;
					ReportWarning ("Unexpected index of region for findable city.");
					continue;
				}

				city.pRegion = vecRegions [cRegion];
			} else {
				const size_t cCountry = pCities [c].wRegionIndex - 1;
				assert (cCountry < vecCountries.size ());
				if (cCountry >= vecCountries.size ()) {
					city.bDeleted = true;
					ReportWarning ("Unexpected index of country for findable city.");
					continue;
				}
				country_t * const pCountry = vecCountries [cCountry];

				// Get 'dummy' region associated with the country.
				region_t * pDummyRegion = NULL;
				const Country2Region_t::iterator dri = DummyRegions.find (pCountry);
				if (dri == DummyRegions.end ()) {
					m_pMap->Regions.push_back (region_t ());
					region_t & region = m_pMap->Regions.back ();
					DummyRegions [pCountry] = & region;

					region.pCountry = pCountry;

					pDummyRegion = & region;
				} else
					pDummyRegion = dri->second;

				city.pRegion = pDummyRegion;
			}
		}
	}

	// Zips.
	fvector<zip_t *> vecZips;
	size_t cZips = 0;
	if (pLblHeader->dwZipsLength) {
		assert (pLblHeader->wZipsRecSize == sizeof (CLblZip));
		cZips = pLblHeader->dwZipsLength/sizeof (CLblZip);
		const CLblZip * const pZips = reinterpret_cast<const CLblZip *> (m_pStart + _sm.cLblOffset + pLblHeader->dwZipsOffset);

		vecZips.resize (cZips);
		for (size_t c = 0; c < cZips; ++ c) {
			m_pMap->Zips.push_back (zip_t ());
			zip_t & zip = m_pMap->Zips.back ();
			vecZips [c] = & zip;

			// Name.
			const DWORD dwCodeOffset = ConvertB3ToDWORD (pZips [c].b3CodeOffset);
			zip.strName.assign (labels [dwCodeOffset << pLblHeader->btOffsetMultiplierPowerOf2]);
		}
	}

	// POI extra properties.
	const BYTE * pPOIExtraData = NULL;
	if (pLblHeader->dwPOIExtraLength)
		pPOIExtraData = m_pStart + _sm.cLblOffset + pLblHeader->dwPOIExtraOffset;

	// Highways.
	size_t cHighways = 0;
	fvector<highway_t *> vecHighways;
	if (pLblHeader->dwHighwaysLength) {
		assert (pLblHeader->wHighwaysRecSize == sizeof (CLblHighway));
		cHighways = pLblHeader->dwHighwaysLength/sizeof (CLblHighway);
		const CLblHighway * const pHighways = reinterpret_cast<const CLblHighway *> (m_pStart + _sm.cLblOffset + pLblHeader->dwHighwaysOffset);

		const CLblHighwayExtra * pHighwayExtra = NULL;
		size_t cHighwayExtras = 0;
		if (pLblHeader->dwHighwayExtraLength) {
			assert (pLblHeader->wHighwayExtraRecSize == sizeof (CLblHighwayExtra));
			cHighwayExtras = pLblHeader->dwHighwayExtraLength/sizeof (CLblHighwayExtra);
			pHighwayExtra = reinterpret_cast<const CLblHighwayExtra *> (m_pStart + _sm.cLblOffset + pLblHeader->dwHighwayExtraOffset);
		}

		vecHighways.resize (cHighways);
		for (size_t c = 0; c < cHighways; ++ c) {
			m_pMap->Highways.push_back (highway_t ());
			highway_t & highway = m_pMap->Highways.back ();
			vecHighways [c] = & highway;

			// Name.
			const DWORD dwNameOffset = ConvertB3ToDWORD (pHighways [c].b3NameOffset) & 0x3FFFFF;
			if (dwNameOffset) {
				highway.strName.assign (labels [dwNameOffset << pLblHeader->btOffsetMultiplierPowerOf2]);
				assert (! highway.strName.empty ());
			} else {
				highway.bDeleted = true;
				continue;
			}

			// Region.
			const size_t cExtra = pHighways [c].wExtraIndex - 1;
			if (cExtra >= cHighwayExtras) {
				highway.bDeleted = true;
				continue;
			}
			const size_t cRegion = pHighwayExtra [cExtra].wRegionIndex - 1;
			if (cRegion < cRegions)
				highway.pRegion = vecRegions [cRegion];
			else
				highway.bDeleted = true;
		}
	}

	// Exit facilities.
	const CLblExitFacility * pExitFacilities = NULL;
	size_t cExitFacilities = 0;
	if (pLblHeader->dwExitsLength) {
		assert (pLblHeader->wExitsRecSize == sizeof (CLblExitFacility));
		cExitFacilities = pLblHeader->dwExitsLength/sizeof (CLblExitFacility);
		pExitFacilities = reinterpret_cast<const CLblExitFacility *> (m_pStart + _sm.cLblOffset + pLblHeader->dwExitsOffset);
	}

	SetProgressStatus (_cProgressStart + (30*_cProgressSize/100));

	///////////////////////////////////////////////////////////
	//
	// Parse NET sub-file.
	//

	const CNetHeader * pNetHeader = NULL;
	const BYTE * pNetData = NULL;
	if (_sm.cNetOffset) {
		pNetHeader = reinterpret_cast<const CNetHeader *> (m_pStart + _sm.cNetOffset);
		if (::memcmp (pNetHeader->strSignature, "GARMIN NET", sizeof (pNetHeader->strSignature)) != 0) {
			ReportWarning (_sm.cNetOffset, "Unexpected format: NET header is not found.");
			return lsOK;
		}
//		if (pNetHeader->wHeaderLength < 0x37)
//			ReportWarning ("Unexpected size of NET header: 0x%04x", pNetHeader->wHeaderLength);

		// NOTE: size is pNetHeader->dwDataLength.
		pNetData = m_pStart + _sm.cNetOffset + pNetHeader->dwDataOffset;
	}

	///////////////////////////////////////////////////////////
	//
	// Parse NOD sub-file.
	//

	const BYTE * pNod1Data = NULL;
	const BYTE * pNod2Data = NULL;
	if (_sm.cNodOffset) {
		const CNodHeader * const pNodHeader = reinterpret_cast<const CNodHeader *> (m_pStart + _sm.cNodOffset);
		if (::memcmp (pNodHeader->strSignature, "GARMIN NOD", sizeof (pNodHeader->strSignature)) != 0) {
			ReportWarning (_sm.cNodOffset, "Unexpected format: NOD header is not found.");
			return lsOK;
		}
		if (pNodHeader->wHeaderLength < 0x3e)
			ReportWarning ("Unexpected size of NOD header: 0x%04x", pNodHeader->wHeaderLength);

		// NOTE: size is pNodHeader->dwDataLength.
		pNod1Data = m_pStart + _sm.cNodOffset + pNodHeader->dwDataOffset;
		pNod2Data = m_pStart + _sm.cNodOffset + pNodHeader->dw2Offset;

//		const CNod3 * const pNod3 = reinterpret_cast<const CNod3 *> (m_pStart + _sm.cNodOffset + pNodHeader->dw3Offset);
	}

	///////////////////////////////////////////////////////////
	//
	// Parse RGN sub-file.
	//

	const CRgnHeader * const pRgnHeader = reinterpret_cast<const CRgnHeader *> (m_pStart + _sm.cRgnOffset);
	if (::memcmp (pRgnHeader->strSignature, "GARMIN RGN", sizeof (pRgnHeader->strSignature)) != 0) {
		ReportWarning (_sm.cRgnOffset, "Unexpected format: RGN header is not found.");
		return lsFailed;
	}
    if (pRgnHeader->wHeaderLength < 0x1D)
		ReportWarning ("Unexpected size of RGN header: 0x%04x", pRgnHeader->wHeaderLength);

    const BYTE * const pRgnData = m_pStart + _sm.cRgnOffset + pRgnHeader->dwDataOffset;
	const size_t cRgnDataLength = pRgnHeader->dwDataLength;

	// Prepare offsets of RGN1.
    for (k = 0; k < cGroups; ++ k) {
		if (g_bStop)
			return lsInterrupted;

		CGroup & group = groups [k];

        group.cPOIsOffset      = -1;
		group.cPOIsLength      = -1;
		group.cPointsOffset    = -1;
		group.cPointsLength    = -1;
		group.cPolylinesOffset = -1;
		group.cPolylinesLength = -1;
		group.cPolygonsOffset  = -1;
		group.cPolygonsLength  = -1;

        if (group.btKinds == 0)    // Empty group.
			continue;

		const bool bHasPOIs      = (group.btKinds & 0x10) != 0;
		const bool bHasPoints    = (group.btKinds & 0x20) != 0;
		const bool bHasPolylines = (group.btKinds & 0x40) != 0;
        const bool bHasPolygons  = (group.btKinds & 0x80) != 0;

        const size_t cAddKinds = bHasPOIs + bHasPoints + bHasPolylines + bHasPolygons - 1;
		assert (group.cRgnOffset < cRgnDataLength);
		const WORD * pOffsets = reinterpret_cast<const WORD *> (pRgnData + group.cRgnOffset);

        if (bHasPOIs)
            group.cPOIsOffset          = group.cRgnOffset + cAddKinds*sizeof (WORD);

        if (bHasPoints) {
            if (! bHasPOIs)
                group.cPointsOffset    = group.cRgnOffset + cAddKinds*sizeof (WORD);
            else
                group.cPointsOffset    = group.cRgnOffset + * (pOffsets ++);
        }

        if (bHasPolylines) {
            if (! bHasPOIs && ! bHasPoints)
                group.cPolylinesOffset = group.cRgnOffset + cAddKinds*sizeof (WORD);
            else
                group.cPolylinesOffset = group.cRgnOffset + * (pOffsets ++);
        }

        if (bHasPolygons) {
            if (cAddKinds == 0)
                group.cPolygonsOffset  = group.cRgnOffset + cAddKinds*sizeof (WORD);
            else
                group.cPolygonsOffset  = group.cRgnOffset + * pOffsets;
        }
	}

	// Prepare lengths of RGN1.
    for (k = 0; k < cGroups; ++ k) {
		if (g_bStop)
			return lsInterrupted;

		CGroup & group = groups [k];
        if (group.btKinds == 0)    // Empty group.
			continue;

		// POI.
        if (group.cPOIsOffset != -1) {
            if (group.cPointsOffset != -1)         // points follow
                group.cPOIsLength = group.cPointsOffset - group.cPOIsOffset;
            else if (group.cPolylinesOffset != -1) // polylines follow
                group.cPOIsLength = group.cPolylinesOffset - group.cPOIsOffset;
            else if (group.cPolygonsOffset != -1)  // polygons follow
                group.cPOIsLength = group.cPolygonsOffset - group.cPOIsOffset;
            else if (k + 1 < cGroups)
				group.cPOIsLength = groups [k + 1].cRgnOffset - group.cPOIsOffset;
			else
				group.cPOIsLength = cRgnDataLength - group.cPOIsOffset;
        }
		// Point.
        if (group.cPointsOffset != -1) {
            if (group.cPolylinesOffset != -1)     // polylines follow
                group.cPointsLength = group.cPolylinesOffset - group.cPointsOffset;
            else if (group.cPolygonsOffset != -1) // polygons follow
                group.cPointsLength = group.cPolygonsOffset - group.cPointsOffset;
            else if (k + 1 < cGroups)
				group.cPointsLength = groups [k + 1].cRgnOffset - group.cPointsOffset;
			else
				group.cPointsLength = cRgnDataLength - group.cPointsOffset;
        }
		// Polyline.
        if (group.cPolylinesOffset != -1) {
            if (group.cPolygonsOffset != -1)      // polygons follow
                group.cPolylinesLength = group.cPolygonsOffset - group.cPolylinesOffset;
            else if (k + 1 < cGroups)
				group.cPolylinesLength = groups [k + 1].cRgnOffset - group.cPolylinesOffset;
			else
				group.cPolylinesLength = cRgnDataLength - group.cPolylinesOffset;
		}
		// Polygon.
        if (group.cPolygonsOffset != -1) {
			if (k + 1 < cGroups)
				group.cPolygonsLength = groups [k + 1].cRgnOffset - group.cPolygonsOffset;
			else
				group.cPolygonsLength = cRgnDataLength - group.cPolygonsOffset;
		}
    }
	SetProgressStatus (_cProgressStart + 30*_cProgressSize/100);

	//
	// Complete names of cities.
	//
	typedef std::map<const CRgnPointData *, city_t *> point2city_t;
	point2city_t point2city;
	for (size_t c = 0; c < cCities; ++ c) {
		if (vecCities [c] == NULL)
			continue;
		city_t & city = * vecCities [c];

		if (pCities [c].bPoint == 0) {
			// NOTE: the name is already determined.
			assert (! city.strName.empty ());
			continue;
		}

		const size_t cGroup = pCities [c].wGroupIndex - 1;
		if (cGroup < cGroups) {
			const CGroup & group = groups [cGroup];
			assert (group.btKinds & 0x20);

			const CRgnPointData * pPointData = reinterpret_cast<const CRgnPointData *> (pRgnData + group.cPointsOffset);
			pPointData += pCities [c].btPointIndex - 1;
			assert (reinterpret_cast<const BYTE *> (pPointData) + sizeof (CRgnPointData) <= pRgnData + group.cPointsOffset + group.cPointsLength);

			assert (pPointData->b3LabelOffset != 0);
			city.strName = labels [pPointData->b3LabelOffset << pLblHeader->btOffsetMultiplierPowerOf2];
			assert (! city.strName.empty ());

			point2city.insert (point2city_t::value_type (pPointData, & city));
		} else {
			ReportWarning ("Unexpected index of group.");
		}
	}

	//
	// Parse POIs.
	//
    for (k = 0; k < cGroups; ++ k) {
		SetProgressStatus (_cProgressStart + (30*_cProgressSize/100) + (10*_cProgressSize/100)*k/cGroups);
		if (g_bStop)
			return lsInterrupted;

		const CGroup & group = groups [k];
        if (group.cPOIsOffset == -1)
			continue;

		assert (group.cPOIsLength != -1);
        for (i = group.cPOIsOffset; i < group.cPOIsOffset + group.cPOIsLength; i += sizeof (CRgnPOIData)) {
			const CRgnPOIData * const pPOIData = reinterpret_cast<const CRgnPOIData *> (pRgnData + i);
			if (reinterpret_cast<const BYTE *> (pPOIData + sizeof (CRgnPOIData)) > m_pEnd)
				break;

			// Label.
			const char * strLabel;
			if (pPOIData->bExtra && pPOIExtraData) {
				const CLblPOIExtra * const pPOIExtra = reinterpret_cast<const CLblPOIExtra *> (pPOIExtraData + (pPOIData->b3LabelOffset << pLblHeader->btOffsetMultiplierPowerOf2));
				if (reinterpret_cast<const BYTE *> (pPOIExtra) + sizeof (CLblPOIExtra) >= m_pEnd)
					break;
				strLabel = pPOIExtra->b3LabelOffset ? labels [pPOIExtra->b3LabelOffset << pLblHeader->btOffsetMultiplierPowerOf2].c_str () : NULL;
			} else
				strLabel = pPOIData->b3LabelOffset  ? labels [pPOIData->b3LabelOffset  << pLblHeader->btOffsetMultiplierPowerOf2].c_str () : NULL;
            // Position.
			point_t pt;
            pt.x = c_fGarminUnit*ConvertB2ToGU (group.cShiftBits, pPOIData->b2X, group.lX0);
            pt.y = c_fGarminUnit*ConvertB2ToGU (group.cShiftBits, pPOIData->b2Y, group.lY0);
            // Type.
            const USHORT ushType = (WORD (pPOIData->btType) << 8) + (pPOIData->bHasSubType ? pPOIData->btSubType : 0);
            // Zoom level.
			const size_t cLevel = group.cLevel;

			if (pPOIData->bHasSubType == 0)
				-- i;

			rgn_t & rgn = AddPoint (
				cLevel, ushType, pt, strLabel,
				reinterpret_cast<const BYTE *> (pPOIData) - m_pStart, sizeof (CRgnPOIData) - (pPOIData->bHasSubType == 0 ? 1 : 0)
			);

			//
			// Extra properties.
			//
			if (pPOIData->bExtra && pPOIExtraData) {
				const CLblPOIExtra * const pPOIExtra = reinterpret_cast<const CLblPOIExtra *> (pPOIExtraData + (pPOIData->b3LabelOffset << pLblHeader->btOffsetMultiplierPowerOf2));
				if (reinterpret_cast<const BYTE *> (pPOIExtra) + sizeof (CLblPOIExtra) >= m_pEnd)
					break;

				size_t cExtraOffset = 3;
				const BYTE * const pExtra = reinterpret_cast<const BYTE *> (pPOIExtra);

				assert (pPOIExtra->bUnknown == 0);

				if (0x20 <= pPOIData->btType && pPOIData->btType <= 0x27) {
					//
					// Exit with facilities.
					//
					cExtraOffset = 3;

					// ??.
					if (pPOIExtra->bHasFlags) {
						// TODO: research.
						const BYTE btFlags = pExtra [cExtraOffset];
//						assert (btFlags == 0xA0);

						++ cExtraOffset;
					}

					// Street.
					const DWORD dwStreetOffset_ = ConvertB3ToDWORD (pExtra + cExtraOffset);
					const DWORD dwStreetOffset = dwStreetOffset_ & 0x3FFFFF;
					if (dwStreetOffset)
						rgn.pExtra->strStreetDesc = labels [dwStreetOffset << pLblHeader->btOffsetMultiplierPowerOf2];

					const bool bHasFacilities = (dwStreetOffset_ & 0x800000) != 0;

					// Overnight parking.
					const bool bOvernightParking = (dwStreetOffset_ & 0x400000) != 0;
					if (bOvernightParking)
						rgn.pExtra->strExtra.append ("OvernightParking=1\r\n");

					cExtraOffset += 3;

					// Highway.
					size_t cHighwayIndex;
					if (cHighways > 255) {
						cHighwayIndex = * reinterpret_cast<const WORD *> (pExtra + cExtraOffset);
						cExtraOffset += 2;
					} else {
						cHighwayIndex = * (pExtra + cExtraOffset);
						++ cExtraOffset;
					}
					if (cHighwayIndex != 0) {
						-- cHighwayIndex;
						assert (cHighwayIndex < cHighways);
						if (cHighwayIndex < cHighways)
							rgn.pExtra->pHighway = vecHighways [cHighwayIndex];
					}

					if (bHasFacilities) {
						// Facilities.
						size_t cFirstFacility;
						if (cExitFacilities > 255) {
							cFirstFacility = * reinterpret_cast<const WORD *> (pExtra + cExtraOffset);
							cExtraOffset += 2;
						} else {
							cFirstFacility = * (pExtra + cExtraOffset);
							++ cExtraOffset;
						}
						-- cFirstFacility;
						assert (cFirstFacility < cExitFacilities);
						if (cFirstFacility < cExitFacilities) {
							const CLblExitFacility * pFacility = pExitFacilities + cFirstFacility;
							size_t cExitCount = 1;
							while (true) {
								// Facility type.
								char buf [32];
								::sprintf (buf, "Exit%d=0x%x,", cExitCount, pFacility->btType);
								rgn.pExtra->strExtra.append (buf);

								// Facility direction.
								switch (pFacility->btDirection) {
									case 0x0: rgn.pExtra->strExtra.append ("N,"); break;
									case 0x1: rgn.pExtra->strExtra.append ("S,"); break;
									case 0x2: rgn.pExtra->strExtra.append ("E,"); break;
									case 0x3: rgn.pExtra->strExtra.append ("W,"); break;
									case 0x4: rgn.pExtra->strExtra.append ("I,"); break;
									case 0x5: rgn.pExtra->strExtra.append ("O,"); break;
									case 0x6: rgn.pExtra->strExtra.append ("B,"); break;
									case 0x7: rgn.pExtra->strExtra.append (","); break;
									default: assert (false);
								}

								// Facilities.
								::sprintf (buf, "0x%x,", pFacility->btFacilities);
								rgn.pExtra->strExtra.append (buf);

								// Description.
								const char * strDescription = pFacility->b3LabelOffset ? labels [pFacility->b3LabelOffset << pLblHeader->btOffsetMultiplierPowerOf2].c_str () : NULL;
								if (strDescription)
									rgn.pExtra->strExtra.append (strDescription);

								rgn.pExtra->strExtra.append ("\r\n");

								assert (pFacility->bUnknown == 0);
								assert (pFacility->bUnknown2 == 0);

								if (pFacility->bLast)
									break;
								++ pFacility;
								++ cExitCount;
							}
						}
					}
				} else if (0x29 <= pPOIData->btType && pPOIData->btType <= 0x30) {
					//
					// Searchable POIs.
					//

					bool bHasNumber = true;
					bool bHasStreet = true;
					bool bHasCity   = true;
					bool bHasZip    = true;
					bool bHasPhone  = true;
					bool bHasExtra1 = false;
					if (pPOIExtra->bHasFlags) {
						bHasNumber = pPOIExtra->bHasNumber;
						bHasStreet = pPOIExtra->bHasStreet;
						bHasCity   = pPOIExtra->bHasCity;
						bHasZip    = pPOIExtra->bHasZip;
						bHasPhone  = pPOIExtra->bHasPhone;
						bHasExtra1 = pPOIExtra->bHasExtra1;

						// TODO: research.
						assert (pPOIExtra->cReserved == 0x2);

						++ cExtraOffset;
					} else {
						bHasNumber = pLblHeader->bPOIExtraDef_HasNumber;
						bHasStreet = pLblHeader->bPOIExtraDef_HasStreet;
						bHasCity   = pLblHeader->bPOIExtraDef_HasCity;
						bHasZip    = pLblHeader->bPOIExtraDef_HasZip;
						bHasPhone  = pLblHeader->bPOIExtraDef_HasPhone;
						bHasExtra1 = pLblHeader->bPOIExtraDef_HasExtra1;
					}
/*CLblPOIExtraDef ExtraDef = pLblHeader->POIExtraDef; 
if (pPOIExtra->bHasFlags){ 
	BYTE btLocalFlags = pPOIExtra->btFlags;
	for (size_t i = 0; i < 8; ++ i) {
		const BYTE btGlobalBit = 1 << i;
		if ((ExtraDef.bt & btGlobalBit) != 0) {
			if ((btLocalFlags & 0x01) == 0)
				ExtraDef.bt &= ~btGlobalBit;
			btLocalFlags >>= 1;
		}
	}
}
bHasNumber = ExtraDef.bHasNumber;
bHasStreet = ExtraDef.bHasStreet;
bHasCity   = ExtraDef.bHasCity;
bHasZip    = ExtraDef.bHasZip;
bHasPhone  = ExtraDef.bHasPhone;
bHasExtra1 = ExtraDef.bHasExtra1;*/

					// Number.
					if (bHasNumber) {
						const BYTE * const pNumber = pExtra + cExtraOffset;
						if ((pNumber [0] & 0x80) == 0) {
//							ReportWarning (pExtra - m_pStart, "Unknown format for street numbering in POI address of '%s'. The number will be lost.", strLabel);
//							rgn.pExtra->strNumber = "";

							// TODO: research.
//							assert (pNumber [0] == 0x07);
// lbl             street    city   zip   phone
// 5E 02 80 - 8B - 7F D1 01 -     - 03  -       - 00    C3 76 76 21 61 17
// 95 5E 02 -    - 00 09 D2 - 01  - 03  -         00 01 C3 76 76 19 30 45 CC
// EF 28 80 - 83 - 7F D1 01 -     - 03  -       - 00 D1 28 80 83 7F D1 01 03 00 
							
//							cExtraOffset += 3;

//12- 3   12 # 3
//12-03   12 # 3
//12-13   12 APT 3
//12-23   12 BLDG 3
//12-33   12 DEPT 3
//12-43   12 FL 3
//12-53   12 RM 3
//12-63   12 STE 3
//12-73   12 UNIT 3
						} else {
							string_t strNumber;
							for (size_t c = 0; c < 6; ++ c) {
								++ cExtraOffset;
								if (pNumber [c] == 0xf8)
									// Omit this really empty byte.
									break;

								const BYTE bt = pNumber [c] & 0x7F;
								const size_t c1 = bt / 11;
								const size_t c2 = bt % 11;
								strNumber.append (1, c1 == 10 ? '-' : '0' + c1);
								if (c > 0 && pNumber [c] & 0x80)
									break;
								strNumber.append (1, c2 == 10 ? '-' : '0' + c2);
							}
							if (strNumber.size () > 1 && strNumber [strNumber.size () - 1] == '-')
								rgn.pExtra->strNumber.assign (strNumber.c_str (), strNumber.size () - 1);
							else
								rgn.pExtra->strNumber.swap (strNumber);
						}
					}
					// Street.
					if (bHasStreet) {
						const DWORD dwStreetOffset = ConvertB3ToDWORD (pExtra + cExtraOffset) & 0x3FFFFF;
						const labels_t::iterator i = labels.find (dwStreetOffset);
						if (i != labels.end ()) {
							rgn.pExtra->strStreetDesc = labels [dwStreetOffset << pLblHeader->btOffsetMultiplierPowerOf2];
						} else {
							assert (true);
						}

						cExtraOffset += 3;
					}

					// City.
					if (bHasCity) {
						size_t cCityIndex;
						if (cCities > 255) {
							cCityIndex = * reinterpret_cast<const WORD *> (pExtra + cExtraOffset);
							cExtraOffset += 2;
						} else {
							cCityIndex = * (pExtra + cExtraOffset);
							++ cExtraOffset;
						}
						-- cCityIndex;

//						assert (cCityIndex < cCities);
						if (cCityIndex < cCities)
							rgn.pExtra->pCity = vecCities [cCityIndex];
						else
							ReportWarning (pExtra - m_pStart, "Unexpected index of city. It will be lost.");
					}

					// Zip.
					if (bHasZip) {
						size_t cZipIndex;
						if (cZips > 255) {
							cZipIndex = * reinterpret_cast<const WORD *> (pExtra + cExtraOffset);
							cExtraOffset += 2;
						} else {
							cZipIndex = * (pExtra + cExtraOffset);
							++ cExtraOffset;
						}
						-- cZipIndex;

//						assert (cZipIndex < cZips);
						if (cZipIndex < cZips)
							rgn.pExtra->pZip = vecZips [cZipIndex];
						else
							ReportWarning (pExtra - m_pStart, "Unexpected index of zip. It will be lost.");
					}

					// Phone.
					if (bHasPhone) {
						const BYTE * const pPhone = pExtra + cExtraOffset;
						if (pPhone [0] & 0x80) {
							string_t strPhone;
							size_t c;
							for (c = 0; c < 10; ++ c) {
								++ cExtraOffset;

								const BYTE bt = pPhone [c] & 0x7F;
								const bool bLast = c > 0 && pPhone [c] & 0x80;

								const size_t c1 = bt / 11;
								const size_t c2 = bt % 11;
								strPhone.append (1, c1 == 10 ? '-' : '0' + c1);
								if (! bLast || c2 != 10)
									strPhone.append (1, c2 == 10 ? '-' : '0' + c2);
								if (bLast)
									break;
							}

							rgn.pExtra->strPhone.swap (strPhone);
						} else {
							// TODO:
//							assert (false);
							ReportWarning (pExtra - m_pStart, "Unknown format of phone number. The number will be lost.");
						}
					}

					// ??.
					if (bHasExtra1) {
//						assert (false);
						// TODO: research.
// 77 45 80 - BE - DD 47 07 - 19 - DE 00 - C3 57 3D 15 11 80 - ! 00 00 00 00 A4 26
// 02 ED F8 -      F4 A3 05 - 13 - 35 01 - C3 57 34 36 0C 94 - ! 00 00 00 00 93 6F
					}
				} else {
					// TODO: research.
//					assert (false); // failed for marine POIs base map.
				}
			}
        }
    }
	SetProgressStatus (_cProgressStart + (40*_cProgressSize/100));

	//
	// Parse points.
	//
	for (k = 0; k < cGroups; ++ k) {
		SetProgressStatus (_cProgressStart + (40*_cProgressSize/100) + (10*_cProgressSize/100)*k/cGroups);
		if (g_bStop)
			return lsInterrupted;

		const CGroup & group = groups [k];
        if (group.cPointsOffset == -1)
			continue;

		assert (group.cPointsLength != -1);
		for (i = group.cPointsOffset; i < group.cPointsOffset + group.cPointsLength; i += sizeof (CRgnPointData)) {
			const CRgnPointData * const pPointData = reinterpret_cast<const CRgnPointData *> (pRgnData + i);
			if (reinterpret_cast<const BYTE *> (pPointData) + sizeof (CRgnPointData) > m_pEnd)
				break;

			// Label.
			const char * const strLabel = pPointData->b3LabelOffset ? labels [pPointData->b3LabelOffset << pLblHeader->btOffsetMultiplierPowerOf2].c_str () : NULL;
			// Position.
			point_t pt;
            pt.x = c_fGarminUnit*ConvertB2ToGU (group.cShiftBits, pPointData->b2X, group.lX0);
            pt.y = c_fGarminUnit*ConvertB2ToGU (group.cShiftBits, pPointData->b2Y, group.lY0);
            // Type.
            const USHORT ushType = pPointData->btType;
//			assert ((0x01 <= ushType && ushType <= 0x15) || ushType == 0x1e || ushType == 0x1f || ushType == 0x28 || ushType == 0x29 || ushType == 0x61);
            // Zoom level.
			const size_t cLevel = group.cLevel;

			assert (pPointData->bHasSubType == 0);
			assert (pPointData->bExtra == 0);

			rgn_t & rgn = AddPoint (
				cLevel, ushType << 8, pt, strLabel,
				reinterpret_cast<const BYTE *> (pPointData) - m_pStart, sizeof (CRgnPointData)
			);

			const point2city_t::const_iterator ip = point2city.find (pPointData);
			if (ip != point2city.end ()) {
				rgn.dwFlags |= rgn_t::fIndexedCity;
				rgn.pExtra->pCity = ip->second;
			}

			// Workaround for cgpsmapper.exe bug (incorrect rectMapBound in TRE header for re-saved WorldMap).
			if (! rectMapBound.Contains (pt)) {
				rectMapBound.Extend (pt);
				fMapMeanX = rectMapBound.MeanX ();
			}
		}
	}
	SetProgressStatus (_cProgressStart + (50*_cProgressSize/100));

	//
	// Parse polylines.
	//

	bool bPointIsNode [256];

    for (k = 0; k < cGroups; ++ k) {
		SetProgressStatus (_cProgressStart + (50*_cProgressSize/100) + (30*_cProgressSize/100)*k/cGroups);
		if (g_bStop)
			return lsInterrupted;

        if (groups [k].cPolylinesOffset == -1)
			continue;

		i = groups [k].cPolylinesOffset;
		assert (groups [k].cPolylinesLength != -1);
		const size_t cEnd = i + groups [k].cPolylinesLength;
		assert (cEnd <= cRgnDataLength);
		while (i + sizeof (CRgnPolylineData) - 1 <= cEnd) {
			const CRgnPolylineData * const pPolylineData = reinterpret_cast<const CRgnPolylineData *> (pRgnData + i);
			if (reinterpret_cast<const BYTE *> (pPolylineData) + sizeof (CRgnPolylineData) - 1 > m_pEnd)
				break;

			// Label.
			const char * strLabel;
			if (pPolylineData->bInNet && pNetData) {
				const CNetData * const pNet = reinterpret_cast<const CNetData *> (pNetData + (pPolylineData->b3LabelOffset << pNetHeader->btDataOffsetMultiplierPowerOf2));
				if (reinterpret_cast<const BYTE *> (pNet) + sizeof (CNetData) >= m_pEnd)
					break;
				strLabel = pNet->b3LabelOffset          ? labels [pNet->b3LabelOffset          << pLblHeader->btOffsetMultiplierPowerOf2].c_str () : NULL;
			} else
				strLabel = pPolylineData->b3LabelOffset ? labels [pPolylineData->b3LabelOffset << pLblHeader->btOffsetMultiplierPowerOf2].c_str () : NULL;
			// First point.
			const long lX0 = ConvertB2ToGU (groups [k].cShiftBits, pPolylineData->b2X0, groups [k].lX0);
			const long lY0 = ConvertB2ToGU (groups [k].cShiftBits, pPolylineData->b2Y0, groups [k].lY0);
			// Type.
			const USHORT ushType = pPolylineData->btType;
			// Zoom level.
			const size_t cLevel = groups [k].cLevel;

			size_t cBitStreamSize; 
			if (pPolylineData->bTwoBytesInLen == 0) {
				cBitStreamSize = pPolylineData->btBitstreamLen;
				i += sizeof (CRgnPolylineData) - 1;
			} else {
				cBitStreamSize = pPolylineData->wBitstreamLen;
				i += sizeof (CRgnPolylineData);
			}

			const BYTE btBitDef = pRgnData [i];
			++ i;
			const BYTE * const pBitStream = pRgnData + i;
			i += cBitStreamSize;
			if (i > cEnd)
				break;

			points_t points;
			GetPointsFromBitstream (pBitStream, cBitStreamSize, groups [k].cShiftBits, btBitDef, pPolylineData->bExtraBit, fMapMeanX, lX0, lY0, points, bPointIsNode);

			rgn_t & rgn = AddPoly (
				cLevel, ushType, rkPolyline, points, strLabel, pPolylineData->bDirIndicator,
				reinterpret_cast<const BYTE *> (pPolylineData) - m_pStart, sizeof (CRgnPolylineData) - (pPolylineData->bTwoBytesInLen == 0 ? 1 : 0) + 1 + cBitStreamSize
			);

			// Read extra attributes from NET and NOD.
			if (pPolylineData->bInNet && pNetData) {
				const BYTE * p = pNetData + (pPolylineData->b3LabelOffset << pNetHeader->btDataOffsetMultiplierPowerOf2);

				// Parse secondary labels.
				while (true) {
					const CNetData * const pNet = reinterpret_cast<const CNetData *> (p);
					if (pNet->bSegmented == 0) {
						const char * strLabel = pNet->b3LabelOffset ? labels [pNet->b3LabelOffset << pLblHeader->btOffsetMultiplierPowerOf2].c_str () : NULL;
						// TODO:
					}
					p += 3;
					if (pNet->bLastLabel == 1)
						break;
				}

				const CNetDataFlags * const pFlags = reinterpret_cast<const CNetDataFlags *> (p);

				// Length in feet.
				p += 3;

				// List of segments.
				while (p < m_pEnd) {
					const BYTE btSegmentsPerLevel = (* p) & 0x7F;
					const bool bLast = ((* p) & 0x80) != 0;
					++ p;

					p += 3*btSegmentsPerLevel;

					if (bLast)
						break;
				}

/*				if (pFlags->bHasAddress) {
# ifdef _DEBUG
					_PrintBytes (strLabel, p, 20);
# endif // _DEBUG

					const BYTE btBlocks = (* p) + 1;
					++ p;

					const CNetAddressFlags * const pPAFlags = reinterpret_cast<const CNetAddressFlags *> (p);
					p += sizeof (CNetAddressFlags);

					// Zip code data.
					switch (pPAFlags->btZipsFmt) {
						case 0: { // Var len.
							const BYTE btLen = * p;
							++ p;
							p += btLen;
							break;
						}
						case 1: // ??
//							assert (false);
							break;
						case 2: { // 1- or 2-byte index.
							WORD wZipIdx;
							if (cZips > 255) {
								wZipIdx = * reinterpret_cast<const WORD *> (p);
								p += sizeof (WORD);
							} else {
								wZipIdx = * p;
								p += sizeof (BYTE);
							}
							if (wZipIdx == 0) {
								// ??
							} else if (wZipIdx <= vecZips.size ())
								rgn.pExtra->pZip = vecZips [wZipIdx - 1];
							break;
						}
						case 3: // Nothing.
							break;
						default:
							assert (false);
							break;
					}

					// City data.
					switch (pPAFlags->btCitiesFmt) {
						case 0: { // Var len.
							const BYTE btLen = * p;
							++ p;
							p += btLen;
							break;
						}
						case 1: // ??
//							assert (false);
							break;
						case 2: { // 1- or 2-byte index.
							WORD wCityIdx;
							if (cCities > 255) {
								wCityIdx = * reinterpret_cast<const WORD *> (p);
								p += sizeof (WORD);
							} else {
								wCityIdx = * p;
								p += sizeof (BYTE);
							}
							if (wCityIdx == 0) {
								// ??
							} else if (wCityIdx <= vecCities.size ())
								rgn.pExtra->pCity = vecCities [wCityIdx - 1];
							break;
						}
						case 3: // Nothing.
							break;
						default:
							assert (false);
							break;
					}

					// House numbers data.
					switch (pPAFlags->btNumbersFmt) {
						case 0: { // Var len.
							const BYTE btLen = * p;
							++ p;

//							_ParseHouseNumbers (rgn, btBlocks, p, btLen);
							p += btLen;
							break;
						}
						case 1: // ??
//							assert (false);
							break;
						case 2: // Plain number??
//							assert (false);
							break;
						case 3: // Nothing.
							break;
						default:
							assert (false);
							break;
					}
				}

				if (pFlags->bHasNodInfo) {
# ifdef _DEBUG
//					_PrintBytes (strLabel, p, 40);
# endif // _DEBUG

					const BYTE btHdr = * p;
					++ p;

					if (pNod2Data != NULL) {
						DWORD dwNod2Offset = 0;
						if ((btHdr & 0x3) == 0x01) {
							dwNod2Offset = * reinterpret_cast<const WORD *> (p);
							p += 2;
						} else if ((btHdr & 0x3) == 0x02) {
							dwNod2Offset = p [0] + (DWORD (p [1]) << 8) + (DWORD (p [2]) << 16);
							p += 3;
						} else 
							assert (false);

# ifdef _DEBUG
						_PrintBytes ("[->NOD2]", pNod2Data + dwNod2Offset, 10);
# endif // _DEBUG

						const CNodRoadData * const pRoadData = reinterpret_cast<const CNodRoadData *> (pNod2Data + dwNod2Offset);

						rgn.pExtra->fSpeedLimit = pRoadData->fSpeedLimit;
						rgn.pExtra->fRouteClass = pRoadData->fRouteClass;
						rgn.pExtra->bOneWay     = pFlags->bOneWay;

						const BYTE * const pNod1 = pNod1Data + pRoadData->b3Nod1Offset;
# ifdef _DEBUG
						_PrintBytes ("[->NOD1]", pNod1, 0x40);
# endif // _DEBUG
					}
				}*/
			}
        }
    }
	SetProgressStatus (_cProgressStart + (80*_cProgressSize/100));

	//
	// Parse polygons.
	//
    for (k = 0; k < cGroups; ++ k) {
		SetProgressStatus (_cProgressStart + (80*_cProgressSize/100) + (20*_cProgressSize/100)*k/cGroups);
		if (g_bStop)
			return lsInterrupted;

        if (groups [k].cPolygonsOffset == -1)
			continue;

        i = groups [k].cPolygonsOffset;
		assert (groups [k].cPolygonsLength != -1);
        const size_t cEnd = i + groups [k].cPolygonsLength;
		assert (cEnd <= cRgnDataLength);
		while (i + sizeof (CRgnPolygonData) - 1 <= cEnd) {
			const CRgnPolygonData * const pPolygonData = reinterpret_cast<const CRgnPolygonData *> (pRgnData + i);
			if (reinterpret_cast<const BYTE *> (pPolygonData) + sizeof (CRgnPolygonData) - 1 > m_pEnd)
				break;

			// Label.
			const char * strLabel;
			if (pPolygonData->bInNet && pNetData) {
				const CNetData * const pNet = reinterpret_cast<const CNetData *> (pNetData + (pPolygonData->b3LabelOffset << pNetHeader->btDataOffsetMultiplierPowerOf2));
				if (reinterpret_cast<const BYTE *> (pNet) + sizeof (CNetData) >= m_pEnd)
					break;
				strLabel = pNet->b3LabelOffset         ? labels [pNet->b3LabelOffset         << pLblHeader->btOffsetMultiplierPowerOf2].c_str () : NULL;
			} else
				strLabel = pPolygonData->b3LabelOffset ? labels [pPolygonData->b3LabelOffset << pLblHeader->btOffsetMultiplierPowerOf2].c_str () : NULL;
			// First point.
			const long lX0 = ConvertB2ToGU (groups [k].cShiftBits, pPolygonData->b2X0, groups [k].lX0);
			const long lY0 = ConvertB2ToGU (groups [k].cShiftBits, pPolygonData->b2Y0, groups [k].lY0);
			// Type.
			const USHORT ushType = pPolygonData->btType;
			// Zoom level.
			const size_t cLevel = groups [k].cLevel;

			assert (pPolygonData->bUnknown == 0);

			size_t cBitStreamSize; 
			if (pPolygonData->bTwoBytesInLen == 0) {
				cBitStreamSize = pPolygonData->btBitstreamLen;
				i += sizeof (CRgnPolygonData) - 1;
			} else {
				cBitStreamSize = pPolygonData->wBitstreamLen;
				i += sizeof (CRgnPolygonData);
			}

			const BYTE btBitDef = pRgnData [i];
			++ i;
			const BYTE * const pBitStream = pRgnData + i;
			i += cBitStreamSize;
			if (i > cEnd)
				break;

			points_t points;
			GetPointsFromBitstream (pBitStream, cBitStreamSize, groups [k].cShiftBits, btBitDef, false, fMapMeanX, lX0, lY0, points);

			AddPoly (
				cLevel, ushType, rkPolygon, points, strLabel, false,
				reinterpret_cast<const BYTE *> (pPolygonData) - m_pStart, sizeof (CRgnPolygonData) - (pPolygonData->bTwoBytesInLen == 0 ? 1 : 0) + 1 + cBitStreamSize
			);

			if (ushType == 0x4b) // Background
				m_pMap->Header.bTransparent = false;
		}
    }
	
# ifdef _DEBUG
	////////////////////////////////////////////////////////////////////////
	//
	// TODO: research.
	//

	if (pLblHeader->dwPOIIndexLength != 0) {
		assert (pLblHeader->wPOIIndexRecSize == sizeof (CLblPOIIndexItem));
		const size_t cPOIIndexSize = pLblHeader->dwPOIIndexLength/sizeof (CLblPOIIndexItem);
		const CLblPOIIndexItem * const pPOIIndex = reinterpret_cast<const CLblPOIIndexItem *> (m_pStart + _sm.cLblOffset + pLblHeader->dwPOIIndexOffset);

		for (size_t c = 0; c < cPOIIndexSize; ++ c) {
			const CLblPOIIndexItem & ii = pPOIIndex [c];

			assert (ii.btPOIIndex != 0);
			const size_t cPOIIndex = ii.btPOIIndex - 1;

			assert (ii.wGroupIndex != 0);
			const size_t cGroup = ii.wGroupIndex - 1;
			assert (cGroup < cGroups);
			const CGroup & group = groups [cGroup];

//			assert (ii.btPOISubType <= 0x10);

			const char * strLabel = NULL;
			USHORT ushType = 0;
			bool bFound = false;

			// Find N-th POI in the group.
			size_t cPOI = 0;
			assert (group.cPOIsLength != -1);
			for (i = group.cPOIsOffset; i < group.cPOIsOffset + group.cPOIsLength; i += sizeof (CRgnPOIData)) {
				const CRgnPOIData * const pPOIData = reinterpret_cast<const CRgnPOIData *> (pRgnData + i);
				if (pPOIData->bHasSubType == 0)
					-- i;

				if (cPOI < cPOIIndex) {
					++ cPOI;
					continue;
				}

				// Label.
				if (pPOIData->bExtra && pPOIExtraData) {
					const CLblPOIExtra * const pPOIExtra = reinterpret_cast<const CLblPOIExtra *> (pPOIExtraData + (pPOIData->b3LabelOffset << pLblHeader->btOffsetMultiplierPowerOf2));
					if (reinterpret_cast<const BYTE *> (pPOIExtra) + sizeof (CLblPOIExtra) >= m_pEnd)
						break;
					strLabel = pPOIExtra->b3LabelOffset ? labels [pPOIExtra->b3LabelOffset << pLblHeader->btOffsetMultiplierPowerOf2].c_str () : NULL;
				} else
					strLabel = pPOIData->b3LabelOffset  ? labels [pPOIData->b3LabelOffset  << pLblHeader->btOffsetMultiplierPowerOf2].c_str () : NULL;

	            // Type.
		        ushType = (WORD (pPOIData->btType) << 8) + (pPOIData->bHasSubType ? pPOIData->btSubType : 0);

				bFound = true;
				break;
			}

			assert (true);
		}
	}

	if (pLblHeader->dwPOITypeIndexLength != 0) {
		assert (pLblHeader->wPOITypeIndexRecSize == sizeof (CLblPOITypeIndexItem));
		const size_t cPOITypeIndexSize = pLblHeader->dwPOITypeIndexLength/sizeof (CLblPOITypeIndexItem);
		const CLblPOITypeIndexItem * const pPOITypeIndexItem = reinterpret_cast<const CLblPOITypeIndexItem *> (m_pStart + _sm.cLblOffset + pLblHeader->dwPOITypeIndexOffset);

		for (size_t c7 = 0; c7 < cPOITypeIndexSize; ++ c7) {
			const DWORD l = ConvertB3ToDWORD (pPOITypeIndexItem [c7].b3StartIndex);
		}
	}
# endif // _DEBUG

	return lsOK;
}
